/*
 * DedicatedDxfFileInputStream.java
 *
 * Created on 4 de Dezembro de 2007, 10:26
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package importadorDXF;

import utility.Utility;
import java.io.*;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import java.util.zip.*;

/**
 *
 * @author rafaelbuck
 */
public class DedicatedDxfFileInputStream
{
    
    // Classe Abstrata Interna
    private abstract class DxfStreamReader
    {

        abstract short readChunk()
            throws NumberFormatException, IOException, DxfException;

        short getGroup()
        {
            return group;
        }

        abstract String getStringData()
            throws DxfException;

        abstract int getIntData()
            throws DxfException;

        abstract double getDoubleData()
            throws DxfException;

        abstract byte[] getByteArrayData()
            throws DxfException;

        abstract int getLongIntData()
            throws DxfException;

        protected short group;

        DxfStreamReader()
        {
        }
    }

    // Leitura do arquivo DXF ASCII
    private class AsciiDxfStreamReader extends DxfStreamReader
    {

        private short readGroupLine()
            throws IOException, NumberFormatException, DxfException
        {
            int ret = 0;
            byte b;
            
            while((b = read()) == 32 || b == 9) ;
            if(b < 48 || b > 57)
                if(b == -1)
                    throw new DxfException("err!EOF", new String[] {
                        String.valueOf(lines)
                    });
                else
                    throw new NumberFormatException();
            do
            {
                ret = 10 * ret + (b - 48);
                b = read();
            } while(b >= 48 && b <= 57);
            for(; b == 32 || b == 9; b = read());
            if(b == 10 || b == 13)
            {
                if(b + read() != 23)
                    pos--;
            } else
            {
                throw new NumberFormatException();
            }
            if(ret > 32767)
                throw new NumberFormatException();
            else
                return (short)ret;
        }

        // Leitura de uma fatia
        short readChunk()
            throws NumberFormatException, IOException, DxfException
        {
            lines++;
            super.group = readGroupLine();
            lines++;
            value = readLine();
            // retorna a variavel group da implementacao da classe abstrata
            return super.group;
        }

        String getStringData()
            throws DxfException
        {
            if(DxfGroups.type(super.group) == 1)
                return value;
            else
                throw new DxfException("err!Format", new String[] {
                    String.valueOf(lines)
                });
        }

        double getDoubleData()
            throws DxfException
        {
            if(DxfGroups.type(super.group) == 3)
            {
                try
                {
                    return Double.valueOf(this.value.trim()).doubleValue();
                }
                catch(NumberFormatException _ex) { }
                try
                {
                    AlternativAsciiDxfStreamReader ar = new AlternativAsciiDxfStreamReader();
                    ar.group = super.group;
                    ar.value = this.value;
                    double value = ar.getDoubleData();
                    reader = ar;
                    return value;
                }
                catch(DxfException _ex)
                {
                    throw new DxfException("err!Format", new String[] {
                        String.valueOf(lines)
                    });
                }
            } else
            {
                throw new DxfException("err!Format", new String[] {
                    String.valueOf(lines)
                });
            }
        }

        int getIntData()
            throws DxfException
        {
            switch(DxfGroups.type(super.group))
            {
            case 2: // '\002'
            case 4: // '\004'
            case 6: // '\006'
                return Integer.parseInt(value.trim());
            }
            throw new DxfException("err!Format", new String[] {
                String.valueOf(lines)
            });
        }

        byte[] getByteArrayData()
            throws DxfException
        {
            throw new DxfException("AsciiDxfStreamReader.getByteArrayData(): Not yet implemented!");
        }

        int getLongIntData()
            throws DxfException
        {
            if(DxfGroups.type(super.group) == 6)
                return Integer.parseInt(value.trim());
            else
                throw new DxfException("err!Format", new String[] {
                    String.valueOf(lines)
                });
        }

        protected String value;

        AsciiDxfStreamReader()
        {
        }
    }

    private class AlternativAsciiDxfStreamReader extends AsciiDxfStreamReader
    {

        double getDoubleData()
            throws DxfException
        {
            if(DxfGroups.type(super.group) == 3)
                try
                {
                    return format.parse(super.value.trim()).doubleValue();
                }
                catch(ParseException _ex)
                {
                    throw new DxfException("err!Format", new String[] {
                        String.valueOf(lines)
                    });
                }
            else
                throw new DxfException("err!Format", new String[] {
                    String.valueOf(lines)
                });
        }

        private NumberFormat format;

        AlternativAsciiDxfStreamReader()
        {
            format = NumberFormat.getNumberInstance(Locale.GERMAN);
        }
    }

    private class BinaryDxfStreamReader extends DxfStreamReader
    {

        short readChunk()
            throws NumberFormatException, IOException, DxfException
        {
            lines++;
            if(shortGroups)
            {
                read(groupNumberBytes);
                super.group = (short)(unsignedShort(groupNumberBytes[1]) << 8 | unsignedShort(groupNumberBytes[0]));
            } else
            {
                byte b = read();
                if(b == -1)
                {
                    read(groupNumberBytes);
                    super.group = (short)(unsignedShort(groupNumberBytes[1]) << 8 | unsignedShort(groupNumberBytes[0]));
                } else
                {
                    super.group = unsignedShort(b);
                }
            }
            lines++;
            int type = DxfGroups.type(super.group);
            length = DedicatedDxfFileInputStream.typeLen[type];
            if(length > 0)
                read(value, 0, length);
            else
            if(length == 0)
            {
                if(type == 1)
                {
                    int i = 0;
                    byte b;
                    while((b = read()) != 0) 
                        value[i++] = b;
                    length = i;
                } else
                if(type == 5)
                {
                    length = read();
                    read(value, 0, length);
                } else
                {
                    throw new DxfException("err!Group", new String[] {
                        String.valueOf(lines - 1)
                    });
                }
            } else
            {
                throw new DxfException("err!Group", new String[] {
                    String.valueOf(lines - 1)
                });
            }
            return super.group;
        }

        String getStringData()
            throws DxfException
        {
            if(DxfGroups.type(super.group) == 1)
                return byteToString(value, 0, length);
            else
                throw new DxfException("err!Format", new String[] {
                    String.valueOf(lines)
                });
        }

        private final long unsignedLong(byte b)
        {
            long l = b;
            return l < 0L ? l + 256L : l;
        }

        private final int unsignedInt(byte b)
        {
            int i = b;
            return i < 0 ? i + 256 : i;
        }

        private final short unsignedShort(byte b)
        {
            short s = b;
            return s < 0 ? (short)(s + 256) : s;
        }

        double getDoubleData()
            throws DxfException
        {
            if(DxfGroups.type(super.group) == 3)
            {
                long dummy = unsignedLong(value[7]) << 56 | unsignedLong(value[6]) << 48 | unsignedLong(value[5]) << 40 | unsignedLong(value[4]) << 32 | unsignedLong(value[3]) << 24 | unsignedLong(value[2]) << 16 | unsignedLong(value[1]) << 8 | unsignedLong(value[0]);
                return Double.longBitsToDouble(dummy);
            } else
            {
                throw new DxfException("err!Format", new String[] {
                    String.valueOf(lines)
                });
            }
        }

        int getIntData()
            throws DxfException
        {
            switch(DxfGroups.type(super.group))
            {
            case 2: // '\002'
            case 4: // '\004'
            case 6: // '\006'
                return unsignedInt(value[1]) << 8 | unsignedInt(value[0]);
            }
            throw new DxfException("err!Format", new String[] {
                String.valueOf(lines)
            });
        }

        byte[] getByteArrayData()
            throws DxfException
        {
            byte ret[] = new byte[length];
            for(int i = length - 1; i >= 0; i--)
                ret[i] = value[i];

            return ret;
        }

        int getLongIntData()
            throws DxfException
        {
            if(DxfGroups.type(super.group) == 6)
            {
                int ret = unsignedInt(value[3]) << 24 | unsignedInt(value[2]) << 16 | unsignedInt(value[1]) << 8 | unsignedInt(value[0]);
                return ret;
            } else
            {
                throw new DxfException("err!Format", new String[] {
                    String.valueOf(lines)
                });
            }
        }

        private byte value[];
        private byte groupNumberBytes[];
        private int length;
        private boolean shortGroups;

        BinaryDxfStreamReader()
            throws DxfException
        {
            value = new byte[256];
            groupNumberBytes = new byte[2];
            shortGroups = false;
            mark(2);
            try
            {
                byte b1 = read();
                byte b2 = read();
                if(b1 != 0)
                    throw new DxfException("err!Format", new String[] {
                        String.valueOf(lines)
                    });
                shortGroups = b2 == 0;
                reset();
            }
            catch(IOException _ex) { }
        }
    }

    // Inicializa o arquivo transformado em Stream
    private void init(InputStream is)
        throws DxfException
    {
        // Verifica se o arquivo suporta os metodos mark e reset
        if(!is.markSupported())
            is = new BufferedInputStream(is);
        
        // Vai ate o byte 256 do arquivo
        is.mark(256);
        
        try
        {
            // Verifica se nao eh um arquivo no formato .zip
            try
            {
                ZipInputStream zip = new ZipInputStream(is);
                ZipEntry entry = zip.getNextEntry();
                if(entry == null)
                    throw new ZipException();
                fileLength = entry.getSize();
                iStream = zip;
                // 0001 0000 (marca que eh um arquivo zip)
                filetype |= 0x10;
            }
            catch(ZipException z)
            {
                // vai para o byte 256 do arquivo
                is.reset();
                is.mark(256);
                try
                {
                    iStream = new GZIPInputStream(is);
                    fileLength = -1L;
                    // 0010 0000 (marca que eh um arquivo gzip)
                    filetype |= 0x20;
                }
                catch(IOException io)
                {
                    // retorna ao inicio do arquivo que nao eh zip nem gzip
                    is.reset();
                    iStream = is;
                }
            }
            
            // preenche o buffer
            byteAvailable();
            // desloca do cabecalho DXF binario 
            mark(binDxfTag.length);
            // le o tag do DXF binario
            byte tag[] = new byte[binDxfTag.length];
            // bota o conteudo no buffer
            read(tag);
            if(Utility.equalBytes(tag, 0, binDxfTag, 0, binDxfTag.length))
            {
                // 1000 0001
                filetype |= 0x81;
            } else
            {
                reset();
                mark(shxTag.length);
                tag = new byte[shxTag.length];
                // bota o conteudo no buffer
                read(tag);
                if(Utility.equalBytes(tag, 0, shxTag, 0, shxTag.length))
                {
                    // 1000 0010
                    filetype |= 0x82;
                } else
                {
                    reset();
                    filetype |= 1;
                }
            }
        }
        catch(IOException iox)
        {
            throw new DxfException("err!IO", new String[] {
                "[input stream]", iox.getMessage()
            });
        }
        // 1000 0000 se o primeiro bit nao estiver setado
        if((filetype & 0x80) != 0)
        {
            // Arquivo DXF em binario
            reader = new BinaryDxfStreamReader();
            filetype |= 0x80;
        } else
        {
            // Arquivo DXF em ASCII
            reader = new AsciiDxfStreamReader();
        }
    }

    /** Creates a new instance of DedicatedDxfFileInputStream */
    // Esse metodo eh utilizado pela classe DxfGroups
    public DedicatedDxfFileInputStream(String fileName)
        throws DxfException
    {
        // Inicializacao das variaveis da classe
        markpos = -1L;
        buffer = new byte[0x10000]; // 0x10000 = 65536
        filetype = 0;
        fileLength = -1L;
        linebuf = new char[256];
        
        // Tenta abrir o arquivo passado como parametro
        // Inicializa o arquivo
        try
        {
            File file = new File(fileName);
            fileLength = file.length();
            init(new FileInputStream(file));
        }
        catch(FileNotFoundException _ex)
        {
            // Arquivo nao encontrado
            throw new DxfException("err!FileNotFound", new String[] {
                fileName
            });
        }
    }

    /** Creates a new instance of DedicatedDxfFileInputStream */
    public DedicatedDxfFileInputStream(InputStream is)
        throws DxfException
    {
        // Inicializacao das variaveis da classe
        markpos = -1L;
        buffer = new byte[0x10000];
        filetype = 0;
        fileLength = -1L;
        linebuf = new char[256];
        
        // Inicializa o arquivo
        init(is);
    }

    protected void finalize()
        throws Throwable
    {
        try
        {
            if(iStream != null)
            {
                iStream.close();
                iStream = null;
            }
        }
        catch(Exception _ex) { }
    }

    // Verifica e Carrega o buffer da Stream
    private final boolean byteAvailable()
        throws IOException
    {
        if(pos < bufused)
            return true;
        if(bufused == -1)
        {
            return false;
        } else
        {
            bufpos += bufused;
            // Le a Stream do arquivo e coloca em um buffer
            // Indica a quantidade de bytes utilizados no buffer
            bufused = iStream.read(buffer);
            // Inicializa posicao do buffer
            pos = 0;
            return bufused != -1;
        }
    }

    // Le um byte
    protected byte read()
        throws IOException, DxfException
    {
        if(!byteAvailable())
            throw new DxfException("err!EOF", new String[] {
                String.valueOf(lines)
            });
        else
            return buffer[pos++];
    }

    String byteToString(byte buf[], int off, int len)
    {
        if(len > linebuf.length)
            linebuf = new char[2 * len];
        for(int i = 0; i < len; i++)
        {
            int b = buf[off + i];
            linebuf[i] = (char)(b >= 0 ? b : b + 256);
        }

        return new String(linebuf, 0, len);
    }

    // Le o buffer de bytes ate seu fim
    protected int read(byte b[])
        throws IOException, DxfException
    {
        return read(b, 0, b.length);
    }

    // Le uma sequencia de bytes
    protected int read(byte cbuf[], int off, int len)
        throws IOException, DxfException
    {
        if(!byteAvailable())
            throw new DxfException("err!EOF", new String[] {
                String.valueOf(lines)
            });
        // usado-atual < comprimento len
        if(len < bufused - pos)
        {
            System.arraycopy(buffer, pos, cbuf, off, len);
            pos += len;
            return len;
        }
        // trata o restante do buffer (chunk)
        int rest = bufused - pos;
        System.arraycopy(buffer, pos, cbuf, off, rest);
        bufpos += bufused;
        pos = bufused = 0;
        // le recursivamente o buffer
        int chunk = iStream.read(cbuf, off + rest, len - rest);
        if(chunk >= 0)
        {
            bufpos += chunk;
            return rest + chunk;
        } else
        {
            bufused = -1;
            return rest;
        }
    }

    
    String readLine()
        throws IOException, DxfException
    {
        byte b = 0;
        int start = pos;
        for(; pos < bufused && (b = buffer[pos]) != 10 && b != 13; pos++);
        if(b == 10 || b == 13)
        {
            pos++;
            String ret = byteToString(buffer, start, pos - start - 1);
            try
            {
                byte c = read();
                if(c + b != 23 && pos > 0)
                    pos--;
            }
            catch(DxfException _ex) { }
            return ret;
        }
        if(pos - start > 0)
        {
            String ret = byteToString(buffer, start, pos - start);
            if(byteAvailable())
                return ret + readLine();
            else
                return ret;
        }
        if(!byteAvailable())
            return null;
        else
            return readLine();
    }

    protected long skip(long n)
        throws IOException
    {
        if(n < (long)(bufused - pos))
        {
            pos += (int)n;
            return n;
        } else
        {
            int rest = bufused - pos;
            n -= rest;
            pos = bufused = 0;
            long skipped = iStream.skip(n);
            bufpos += skipped;
            return (long)rest + skipped;
        }
    }

    synchronized void mark(int readlimit)
    {
        if(readlimit >= bufused - pos)
            iStream.mark(readlimit);
        markpos = bufpos + (long)pos;
    }

    synchronized void reset()
        throws IOException
    {
        if(markpos < bufpos)
        {
            iStream.reset();
            bufused = 0;
        }
        pos = (int)(markpos - bufpos);
    }

    long getFilePointer()
    {
        return bufpos + (long)pos;
    }

    long getFileLength()
    {
        return fileLength;
    }

    int getLines()
    {
        return lines;
    }

    String getString()
        throws DxfException
    {
        return reader.getStringData();
    }

    float getFloat()
        throws DxfException
    {
        return (float)reader.getDoubleData();
    }

    int getInt()
        throws DxfException
    {
        return reader.getIntData();
    }

    short getColor()
        throws DxfException
    {
        return (short)reader.getIntData();
    }

    short read2Lines()
        throws NumberFormatException, IOException, DxfException
    {
        return reader.readChunk();
    }

    boolean isBinary()
    {
        return (filetype & 0x80) != 0;
    }

    int getType()
    {
        return filetype;
    }

    private static final byte binDxfTag[] = {
        65, 117, 116, 111, 67, 65, 68, 32, 66, 105, 
        110, 97, 114, 121, 32, 68, 88, 70, 13, 10, 
        26, 0
    };
    private static final byte shxTag[] = {
        65, 117, 116, 111, 67, 65, 68, 45, 56, 54, 
        32, 115, 104, 97, 112, 101, 115, 32, 49, 46, 
        48, 13, 10, 26, 0
    };
    private static final int BUFFER_SIZE = 0x10000;
    public static final int TYPE_UNKNOWN = 0;
    public static final int TYPE_DXF = 1;
    public static final int TYPE_SHX = 2;
    public static final int TYPE_ZIP = 16;
    public static final int TYPE_GZIP = 32;
    public static final int TYPE_PACKED = 48;
    public static final int TYPE_BINARY = 128;
    static final int typeLen[] = {
        -1, 0, 2, 8, 2, 0, 4
    };
    protected int pos;
    protected long markpos;
    protected byte buffer[];
    protected long bufpos;
    protected int bufused;
    protected int filetype;
    protected long fileLength;
    private char linebuf[];
    private int lines;
    private InputStream iStream;
    private DxfStreamReader reader;



    /*
     *Este arquivo eh final, nao instanciando mais nenhuma classe do pacote
     *Assim, ele realiza a leitura definitiva do arquivo DXF
     */

}